<div xmlns:th="http://www.thymeleaf.org">
    <div id="body">
        <div th:if="${markdown.size > 15000}" id="wrap-mask"><div class="boxLoading"></div></div>
        <div class="article-header">
            <div class="article-background"></div>
            <div class="lazyload-container" th:style="${markdown.cover !=null ? 'background-image: url('+markdown.cover+'?o=thumbnail)': 'background-image: url('+ setting.backgroundSite+'?o=thumbnail)'}">
                <img style="display: none"
                     th:src="${markdown.cover !=null ? markdown.cover+'?o=thumbnail' : setting.backgroundSite+'?o=thumbnail'}"
                     th:data-src="${markdown.cover !=null ? markdown.cover : setting.backgroundSite}"
                     onload="javascript:loadBanner(this,document.querySelector('.article-header'))"
                />
            </div>
            <div class="inner">
                <div class="blog-title"><span id="article-title" th:text="${markdown.name}"></span><span class="typed-cursor">_</span></div>
                <div class="blog-description font-mono">
                    <a class="edit" itemprop="name" th:href="'/author/'+${markdown.username}" rel="author" th:text="${markdown.username}"></a>
                    <span th:text="'· '+${markdown.uploadTime()}"></span>
                    <span th:if="${visits != null}">
                   · 阅读: <span th:text="${visits}"></span>
                </span>
                    <span class="link"  th:if="${markdown.categories != null && markdown.categories.size() > 0}">
                    ·
                    <a th:each="categoryDO,categoryStat : ${markdown.categories}">
                        <span th:text="${categoryStat.index > 0?' · ':''}"></span><a th:href="'/categories/'+${categoryDO.name}" th:text="${categoryDO.name}"></a>
                    </a>
                </span>
                    <span class="edit"  th:if="${markdown.editable != null && markdown.editable}">
                    ·
                    <a th:href="'/setting/website/manager-articles?operation=editor&id='+${markdown.id}" target="_blank">编辑</a>
                </span>
                    <div class="meta-item-tag" th:if="${markdown.tags != null && markdown.tags.size() > 0}">
                        <svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-tag"></use>
                        </svg>
                        <span th:each="tag,tagStat : ${markdown.tags}">
                        <span th:text="${tagStat.index > 0?' · ':''}"></span><a th:href="'/tags/'+${tag.slug}" th:text="${tag.name}"></a>
                    </span>
                    </div>
                </div>
            </div>
        </div>
        <div class="body-wrapper">
            <el-main id="main_body" class="l_main article_l_main" style="width: 100%;">
                <div id="preview" class="vditor-reset article-body" th:utext="${markdown.html}">
                </div>
                <div class="back-top" style="display: none">
                    <svg class="icon">
                        <use xlink:href="#icon-Up"></use>
                    </svg>
                </div>
            </el-main>
            <div th:if="!${markdown.alonePage}" id="article_tail" th:insert="~{component/article-tail.html}"></div>
            <div class="right-bj" style="display: none">
                <div id="toc-affix">
                    <div class="slimScrollDiv">
                        <div class="toc-content">
                            <header class="toc-header">
                                <svg class="icon" aria-hidden="true">
                                    <use xlink:href="#icon-service-directory"></use>
                                </svg>
                                <span>目录</span>
                            </header>
                        </div>
                        <div class="right-menu">
                            <div class="j-titleList titleList">
                                <div class="j-bj"></div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="show-content">
            <svg class="icon">
                <use xlink:href="#icon-service-directory"></use>
            </svg>
        </div>
        <div class="back-top" style="display: none">
            <svg class="icon" aria-hidden="true"><use xlink:href="#icon-Up"></use></svg>
        </div>
    </div>
    <script th:inline="javascript">
        $(document).ready(function(){
            document.title = [[${titleName}]]
            // 是否为独立页面
            const isAlonePage = [[${markdown.alonePage}]]
            // 是否在点击目录
            let clickingToc = false, justLoading = false
            // 目录最大高度
            let tocMaxHeight = 0
            // 页面滚动位置
            let beforeScrollTop = document.documentElement.scrollTop;
            let scrollAfterTimer = null
            let rightBj = $('.right-bj')
            let main = $('#main_body')
            // 目录
            let contents = []

            // 渲染目录
            // renderContents()
            // 添加更新时间
            if(!isAlonePage){
                addUpdateTime()
            }

            /***
             * 初始化markdown解析器
             */
            Vditor.preview(document.getElementById('preview'),
                [[${markdown.contentText}]], {
                    hljs: {
                        // 是否显示行号
                        lineNumber: window.innerWidth > 768,
                        style: 'native'
                    },
                    theme: {
                        current: 'light'
                    },
                    cdn: `${window.location.origin}/articles/vditor@3.9.3`,
                    transform(htmlStr) {
                        renderContents($(htmlStr))
                        return htmlStr;
                    },
                    after() {
                        setContentTheme()
                        // 添加更新时间
                        if(!isAlonePage){
                            addUpdateTime()
                            // 添加文章尾部信息
                            document.getElementById('preview').append(document.getElementById('article_tail'))
                        }
                        setTimeout(function (){
                            // 隐藏加载动画
                            $('#wrap-mask').hide()
                        }, 0)
                        justLoading = true
                        setTimeout(function (){
                            justLoading = false
                            keepTocVisible()
                        }, 800)
                        contentAfter()
                    }
                })

            /***
             * 创建目录项
             * 根据最大号标题整体向前移, 例如: 最大号标题为h3,则h3为变为h1,h4变为h2
             */
            function titleOneItem(index, $this, tagName, maxTitleNumber){
                const h = tagName.substring(1,2) - maxTitleNumber
                if(index === 0){
                    return '<li first class="pl h-'+h+'" title="'+$this.text()+'"><a href="#'+$this.attr('id')+'"><xmp>'+$this.text()+'</xmp></a></li>'
                }
                return '<li class="pl h-'+h+'" title="'+$this.text()+'"><a href="#'+$this.attr('id')+'"><xmp>'+$this.text()+'</xmp></a></li>'
            }

            //监听整个页面的 copy 事件 去掉复制的代码末尾多余的空格
            document.addEventListener('copy',function(e){
                // clipboardData 对象是为通过编辑菜单、快捷菜单和快捷键执行的编辑操作所保留的，也就是你复制或者剪切内容
                let clipboardData = e.clipboardData || window.clipboardData;
                // 如果 未复制或者未剪切，直接 return
                if(!clipboardData) return ;
                // Selection 对象 表示用户选择的文本范围或光标的当前位置。
                // 声明一个变量接收 -- 用户输入的剪切或者复制的文本转化为字符串
                let text = window.getSelection().toString();
                if(text){
                    // 如果文本存在，首先取消默认行为
                    e.preventDefault();
                    // 通过调用 clipboardData 对象的 setData(format,data) 方法，设置相关文本
                    // format 一个 DOMString 类型 表示要添加到 drag object 的拖动数据的类型
                    // data 一个 DOMString 表示要添加到 drag object 的数据
                    clipboardData.setData('text/plain', text.trim())
                }
            })

            /***
             * 在文章最前面添加更新时间
             */
            function addUpdateTime(){
                let preview = document.getElementById('preview')
                let p = document.createElement("p")
                p.className = "note-update-date"
                p.innerText = `本文最后更新于：${[[${markdown.updateTime()}]]}`
                preview.insertBefore(p , preview.childNodes[0])
            }

            /***
             * 渲染目录
             */
            function renderContents(article) {
                let headerList = ["H1","H2","H3","H4","H5","H6"];
                let headers = []
                article.each(function () {
                    let tagName = $(this).prop("tagName")
                    if (tagName && headerList.indexOf(tagName) > -1) {
                        headers.push($(this))
                    }
                })
                if(!headers || headers.length < 2){
                    return
                }
                main.css("width", '')
                $('.body-wrapper').addClass('body-wrapper-offset')
                // 显示目录按钮
                if(window.innerWidth <= 768){
                    $('.show-content').css('display','flex')
                }
                // 找出最大号的标题, 默认最小为6号标题(h6)
                let maxTitleNumber = 6
                headers.forEach(function (header) {
                    const h = header.prop("tagName").substring(1,2)
                    if(maxTitleNumber > h){
                        maxTitleNumber = h
                    }
                })
                maxTitleNumber--
                headers.forEach(function (header, index) {
                    contents.push(titleOneItem(index, header, header.prop("tagName"), maxTitleNumber))
                })
                $('.j-titleList').prepend(contents)
                // 获取id数组
                for (let i = 0; i < contents.length; i++) {
                    contents[i] = contents[i].replace(/.*?#(.*)".*/g, '$1')
                }
                // 去掉id为空的条目
                contents = contents.filter(content => content && content.length > 0)
                if(contents.length > 0){
                    rightBj.show()
                    main.attr("content","show")
                    renderContentsAfter(contents)
                } else {
                    main.attr("content","hide")
                }
            }

            /***
             * 判断上滑还是下滑
             */
            function checkDirection($this){
                let afterScrollTop = document.documentElement.scrollTop
                let delta = afterScrollTop - beforeScrollTop
                beforeScrollTop = afterScrollTop
                let scrollTop = $this.scrollTop()
                let windowHeight = $this.height()
                // //滚动到底部
                if (scrollTop + windowHeight > $(document).height() - 10) {
                    afterDirection('bottom')
                    return
                }
                if (afterScrollTop < 10 || afterScrollTop > $(document.body).height - 10) {
                    afterDirection('up')
                } else {
                    if (Math.abs(delta) < 10) {
                        return false
                    }
                    afterDirection(delta > 0 ? "down" : "up")
                }
            }
            /***
             * 上滑或下滑后执行的操作
             */
            function afterDirection (direction) {
                // 判断是上滑显示,下滑隐藏
                const top = $('#top')
                const toggleNav = $('#toggle-nav')
                const body = document.getElementById("body")
                if(direction === 'down'){
                    top.removeClass('animateIn')
                    top.addClass('animateOut')
                    toggleNav.removeClass('animateRight')
                    toggleNav.addClass('animateLeft')
                }
                if(direction === 'up'){
                    top.removeClass('animateOut')
                    top.addClass('animateIn')
                    toggleNav.removeClass('animateLeft')
                    toggleNav.addClass('animateRight')
                }
            }

            /***
             * 渲染目录后的操作
             */
            function renderContentsAfter(){
                let $root = $('html, body')
                $('.j-titleList li').on("click", function () {
                    clickingToc = true
                    let top = 75
                    if (typeof($(this).attr("first"))!=="undefined") {
                        // 点击第一个目录
                        top = 168
                    }
                    let href = $.attr(this.querySelector("a"), 'href')
                    $root.animate({
                        scrollTop: $(href).offset().top - top
                    }, 400)
                    setTimeout(function (){
                        clickingToc = false
                    }, 450)
                    return false
                })
                // 滚动到响应的锚点
                setTimeout(function (){
                    if(window.location.hash){
                        $root.animate({
                            scrollTop: $(decodeURIComponent(window.location.hash)).offset().top - 75
                        }, 0)
                    }
                },100)
            }

            /***
             * 显示返回顶部按钮
             */
            function showBackTop() {
                if(document.documentElement.scrollTop > 300){
                    $('.back-top').fadeIn(500)
                } else {
                    $('.back-top').fadeOut(500)
                }
            }
            showBackTop()

            /***
             * 设置目录的最大高度
             */
            function setTocMaxHeight(){
                tocMaxHeight = document.documentElement.clientHeight - 180
                document.body.querySelector('.right-bj .slimScrollDiv>.right-menu').style.maxHeight = tocMaxHeight + 'px'
            }
            setTocMaxHeight()

            /***
             * 目录跟随滚动
             */
            function followScrollToc(){
                if(contents.length < 1){
                    return
                }
                if(rightBj.offset().top - $(window).scrollTop() <=0 ){
                    $('#toc-affix').addClass('affix')
                } else {
                    $('#toc-affix').removeClass('affix')
                }
                const tocAction = $('.j-bj')
                for (let i = 0; i < contents.length; i++) {
                    if ($(window).scrollTop() > $('#' + contents[i]).offset().top - 100 || $(this).scrollTop() + $(this).height() === $(document).height()) {
                        $('.j-titleList').find('li').eq(i).addClass('active').siblings('li').removeClass('active');
                        tocAction.css('top', i * 34)
                    }
                }
                if(scrollAfterTimer != null){
                    clearTimeout(scrollAfterTimer)
                }
                scrollAfterTimer = setTimeout(function (){
                    pushHistory()
                    keepTocVisible()
                }, 150)
            }
            /***
             * 重设window.history
             */
            function pushHistory() {
                const hash = $('.j-titleList').find('li.active').find('a').attr('href')
                const clientHeight = document.documentElement.clientHeight
                if (hash && $(window).scrollTop() > clientHeight - clientHeight*0.7) {
                    window.history.replaceState(null, null, document.location.pathname + hash)
                } else {
                    window.history.replaceState(null, null, document.location.pathname)
                }
            }
            /***
             * 保持目录当前相在可见范围内
             */
            function keepTocVisible() {
                if(clickingToc || justLoading || contents.length < 1){
                    return
                }
                const tocHeader = $('.toc-header')
                if(tocHeader.length < 1){
                    return
                }
                const tocAction = $('.j-bj')
                const tocScroll = $('.slimScrollDiv')
                const tocScrollTop = tocScroll.scrollTop()
                const tocHeaderTop = tocHeader.offset().top - $(window).scrollTop()
                if (tocHeaderTop > 70 && window.innerWidth > 768) {
                    // 隐藏返回顶部按钮
                    $('.back-top').fadeOut(500)
                }
                const tocActionTop = tocAction.offset().top - $(window).scrollTop() - tocHeaderTop - 41
                if (tocActionTop > (tocMaxHeight - 40)) {
                    tocScroll.animate({
                        scrollTop: tocScrollTop + (tocActionTop - tocMaxHeight) + 160
                    }, 300)
                }
                if (tocActionTop < 40) {
                    let top = Math.abs(tocActionTop) + 160
                    tocScroll.animate({
                        scrollTop: tocScrollTop - top
                    }, 300)
                }
            }

            /***
             * 改变top bar 进度条
             */
            function changeTopBar(){
                let scrollHeight = $(document).height() - window.innerHeight
                let progress = ((document.documentElement.scrollTop)/scrollHeight) * 100
                document.querySelector(".scrollbar").style.width = progress +'%'
            }

            function contentAfter(){
                $(window).on('scroll', function (){
                    changeTopBar()
                    // 判断是上滑还是下滑
                    checkDirection($(this))
                    // 显示回到顶部按钮
                    showBackTop()
                    // 目录跟随滚动
                    followScrollToc()
                })

                const slimScrollDiv = $('.slimScrollDiv')
                let startY;
                slimScrollDiv.on('touchstart',function(event){
                    //滑动起点的坐标
                    startY = event.targetTouches[0].pageY
                });

                // 当目录滚动到达顶部/底部时，防止父元素滚动
                slimScrollDiv.on('DOMMouseScroll mousewheel touchmove', function(ev) {
                    let scrollTop = this.scrollTop,
                        scrollHeight = this.scrollHeight,
                        height = $(this).innerHeight(),
                        delta = ev.originalEvent.wheelDelta;
                    if (scrollHeight < tocMaxHeight) {
                        return
                    }

                    if(ev.type === 'touchmove'){
                        delta = ev.targetTouches[0].pageY - startY
                    }
                    let up = delta > 0

                    let prevent = function() {
                        ev.stopPropagation();
                        ev.preventDefault();
                        if(ev.type !== 'touchmove'){
                            ev.returnValue = false;
                        }
                        return false;
                    }
                    if (!up && -delta > scrollHeight - height - scrollTop) {
                        // 向下滚动
                        $(this).scrollTop(scrollHeight);
                        return prevent();
                    } else if (up && delta > scrollTop) {
                        // 向上滚动
                        $(this).scrollTop(0);
                        return prevent();
                    }
                });

                $(window).on("resize", function (){
                    setTocMaxHeight()
                    setBackTopPosition()
                })

                /***
                 * 改变返回顶部按钮位置
                 */
                function setBackTopPosition(){
                    if(window.innerWidth > 768){
                        $('.back-top').css('left', main.width() + main.offset().left + 30)
                    } else {
                        $('.back-top').css('left', '')
                    }
                }
                setBackTopPosition()

                // 点击回到顶部
                $(".back-top").click(function(){
                    $('html, body').animate({
                        scrollTop: 0
                    }, 400)
                });

                // 点击显示目录
                $(".show-content").click(function (){
                    let translateX = slimScrollDiv.css("transform")
                    const reg = '\\((.+?)\\)'
                    translateX = parseFloat(translateX.match(reg)[1].split(',')[4])
                    if(translateX > 150){
                        slimScrollDiv.css("transform", "translateX(0px)")
                    }
                    if(translateX < 150){
                        slimScrollDiv.css("transform", "translateX(250px)")
                    }
                })
            }
            function setContentTheme(){
                const isDark = getThemeCSSName() === 'dark'
                Vditor.setContentTheme(isDark ? 'dark': 'light', `${origin}/articles/vditor@3.9.3/dist/css/content-theme`)
                Vditor.setCodeTheme(isDark ? 'native': 'github', `${origin}/articles/vditor@3.9.3`)
                // Vditor.highlightRender({cnd: `${origin}/articles/vditor@3.9.3`})
            }

            // common.js
            /***
             * 动态打字效果
             */
            let divTyping = $('#article-title')
            let i = 0,timer = 0,str = divTyping.text()
            function typing () {
                if (i <= str.length) {
                    divTyping.text(str.slice(0, i++))
                    if(divTyping.css('display') === 'none'){
                        divTyping.css('display', 'contents')
                    }
                    timer = setTimeout(typing, 80)
                } else {
                    clearTimeout(timer)
                }
            }
            setTimeout(typing, 500)
            let labelIcon = $('.changeTheme i')
            let labelTitle = $('.changeTheme span')
            // 添加暗色主题
            function addDarkTheme() {
                const themeDark = $('#theme-css-dark')
                if(themeDark.length > 0){
                    return
                }
                let link = document.createElement('link');
                link.type = 'text/css';
                link.id = "theme-css-dark";  // 加上id方便后面好查找到进行删除
                link.rel = 'stylesheet';
                link.href = '/articles/css/dark/index-2.0.2.css';
                document.getElementsByTagName("head")[0].appendChild(link);
                labelIcon.removeClass('fa-moon')
                labelIcon.addClass('fa-sun')
                labelTitle.html('亮色')
            }
            // 删除暗色主题
            function removeDarkTheme() {
                const themeDark = $('#theme-css-dark')
                if(themeDark.length > 0){
                    themeDark.remove();
                    labelIcon.removeClass('fa-sun')
                    labelIcon.addClass('fa-moon')
                    labelTitle.html('暗色')
                }
            }
            // 使用暗色主题(记录选择到cookie中)
            function useDarkTheme(useDark) {
                setCookie('jmal-theme', useDark ? "dark" : "light");
                if (useDark) {
                    addDarkTheme();
                } else {
                    removeDarkTheme();
                }
                setContentTheme()
            }
            // 获取cookie中选中的主题名称，没有就给个默认的
            function getThemeCSSName() {
                return getCookie('jmal-theme') || "light";
            }
            $('.changeTheme').click(function (){
                useDarkTheme(getThemeCSSName() === 'light')
                // 记录切换主题的时间戳
                setCookie('changeTheme', new Date().getTime())
            })
            let media = window.matchMedia('(prefers-color-scheme: dark)');
            // 判断系统是否为深色模式
            if (media && media.matches) {
                if(getCookie('changeTheme')){
                    // 上次切换主题的时间距离现在超过8小时就是使用系统默认主题
                    if(new Date().getTime() - getCookie('changeTheme') > 3600 * 1000 * 8){
                        useDarkTheme(true)
                    }
                } else {
                    useDarkTheme(true)
                }
            }
            let callback = (e) => {
                if (e.matches) {
                    useDarkTheme(true)
                } else {
                    useDarkTheme(false)
                }
            };
            if (typeof media.addEventListener === 'function') {
                media.addEventListener('change', callback);
            } else if (typeof media.addListener === 'function') {
                media.addListener(callback);
            }

            function setCookie(name, value) {
                document.cookie = name + "=" + value + ";path=/";
            }

            function getCookie(cname) {
                let name = cname + "=";
                let decodedCookie = decodeURIComponent(document.cookie);
                let ca = decodedCookie.split(';');
                for(let i = 0; i <ca.length; i++) {
                    let c = ca[i];
                    while (c.charAt(0) === ' ') {
                        c = c.substring(1);
                    }
                    if (c.indexOf(name) === 0) {
                        return c.substring(name.length, c.length);
                    }
                }
                return "";
            }
        });
    </script>
</div>

